package asteroids.model;
 
import java.io.*;
import java.net.URL;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.antlr.v4.runtime.RecognitionException;

import asteroids.CollisionListener;
import asteroids.IFacade;
import asteroids.ModelException;
import asteroids.model.programs.*;
import asteroids.model.programs.parsing.ProgramParser;
import asteroids.model.programs.statements.*;
 
public  class Facade implements IFacade<World, Ship, Asteroid, Bullet, Program>{
	
	
	public World createWorld(double width, double height) {
		World world = null;
		 try{world = new World(width,height);}
         catch(Exception exc){
                 throw new ModelException(exc);
         }
         return (World) world;
	}

	/**
	   * Return the width of <code>world</code>.
	   */
	  public double getWorldWidth(World world) {
		return world.getWidth();
	}

	  /**
	   * Return the height of <code>world</code>.
	   */
	  public double getWorldHeight(World world) {
		return world.getHeight();
	}

	  /**
	   * Return all ships located within <code>world</code>.
	   */
	  public Set<Ship> getShips(World world) {
		return world.getAllShips();
	}

	  /**
	   * Return all asteroids located in <code>world</code>.
	   */
	  public Set<Asteroid> getAsteroids(World world) {
		  return world.getAllAsteroids();
	}

	  /**
	   * Return all bullets located in <code>world</code>.
	   */
	  public Set<Bullet> getBullets(World world) {
		  return world.getAllBullets();
	}

	  /**
	   * Add <code>ship</code> to <code>world</code>.
	   */
	  public void addShip(World world, Ship ship){
		  try {world.addAsSpaceObject(ship);
		  
		  }catch(Exception exc) {
			  throw new ModelException("Not a valid ship");
		  }
	  }

	  /**
	   * Add <code>asteroid</code> to <code>world</code>.
	   */
	  public void addAsteroid(World world, Asteroid asteroid){
		  try {world.addAsSpaceObject(asteroid);
		  
		  }catch(Exception exc) {
			  throw new ModelException("Not a valid asteroid");
		  }
	  }

	  /**
	   * Remove <code>ship</code> from <code>world</code>.
	   */
	  public void removeShip(World world, Ship ship){
		  world.removeAsSpaceObject(ship);
	  }

	  /**
	   * Remove <code>asteroid</code> from <code>world</code>.
	   */
	  public void removeAsteroid(World world, Asteroid asteroid){
		  world.removeAsSpaceObject(asteroid);
	  }

	  /**
	   * Advance <code>world</code> by <code>dt<code> seconds. 
	   * 
	   * To enable explosions within the UI, notify <code>collisionListener</code>
	   * whenever an entity collides with a boundary or another entity during this
	   * method. <code>collisionListener</code> may be null. If
	   * <code>collisionListener</code> is <code>null</code>, do not call its notify
	   * methods.
	   */
	  public void evolve(World world, double dt, CollisionListener collisionListener) {
		  if(world == null)
			  throw new ModelException("Invalid world.");
		  world.evolve(dt,collisionListener);
	}

	  /**
	   * Create a new non-null ship with the given position, velocity, radius,
	   * direction and mass.
	   * 
	   * The thruster of the new ship is initially inactive. The ship is not located
	   * in a world.
	   */
       
	  public Ship createShip(double x, double y, double xVelocity, double yVelocity, double radius, double angle, double mass){
          Ship ship = null;
          try{ship = new Ship(x,y,xVelocity,yVelocity,radius,angle, mass);}
          catch(Exception exc){
                  throw new ModelException(exc);
          }
          return (Ship) ship; 
	   }
	  
        public Ship createShip(){
            return createShip(0,0,0,0,1,0,0);
        }
        
        /**
         * Check whether <code>o</code> is a ship.
         * 
         * You can use the <code>instanceof</code> operator to implement this method.
         */
        public boolean isShip(Object o) {
        	return ((SpaceObject)o).isShip();
		}
        
          /**
           * Return the x-coordinate of the ship.
           */
          public double getShipX(Ship ship) {
                 return ((Ship) ship).getPosition().getX();
          }
 
          /**
           * Return the y-coordinate of the ship.
           */
          public double getShipY(Ship ship){
                  return ((Ship) ship).getPosition().getY();
          }
 
          /**
           * Return the velocity of ship along the X-axis.
           */
          public double getShipXVelocity(Ship ship){
                  return ((Ship) ship).getVelocity().getX();
          }
 
          /**
           * Return the velocity of ship along the Y-axis.
           */
          public double getShipYVelocity(Ship ship){
                  return ((Ship) ship).getVelocity().getY();
          }
         
          /**
           * Return the radius of the ship.
           */
          public double getShipRadius(Ship ship){
                  return ((Ship) ship).getRadius();
          }
 
          /**
           * Return the direction of the ship (in radians).
           */
          public double getShipDirection(Ship ship){
                  return ((Ship) ship).getDirection().getAngle();
          }
          
          
          public double getShipMass(Ship ship){
        	  return ship.getMass();
          }

          /**
           * Return the world of <code>ship</code>.
           */
          /**
           * Return the world of <code>ship</code>.
           */
          public World getShipWorld(Ship ship){
        	  return ship.getWorld();
          }

          /**
           * Return whether <code>ship</code>'s thruster is active.
           */
          public boolean isShipThrusterActive(Ship ship){
        	  return ship.getThrustersActivationState();
          }

          /**
           * Enables or disables <code>ship</code>'s thruster depending on the value of
           * the parameter <code>active</code>.
           */
          public void setThrusterActive(Ship ship, boolean active){
        	  ship.changeThrustersActivationState(active);

          }
 
          /**
           * Update the direction of the ship by adding an angle (in
           * radians) to its current direction. The angle may be negative.
           */
          public void turn(Ship ship, double angle){
                  ((Ship) ship).turn(angle);  
          }
 
          
          /**
           * <code>ship</code> fires a bullet.
           */
          public void fireBullet(Ship ship) {
    	  	try {
				ship.fireBullet();
			} catch (Exception exc) {
				throw new ModelException(exc);
			}
		}

          /**
           * Create a new non-null asteroid with the given position, velocity and
           * radius.
           * 
           * The asteroid is not located in a world.
           */
          public Asteroid createAsteroid(double x, double y, double xVelocity, double yVelocity, double radius){
        	  Asteroid asteroid = null;
              try{asteroid = new Asteroid(x,y,xVelocity, yVelocity, radius);
              }
              catch(Exception exc){
                      throw new ModelException(exc);
              }
              return asteroid;
          }

          /**
           * Create a new non-null asteroid with the given position, velocity and
           * radius.
           * 
           * The asteroid is not located in a world.
           * 
           * Use numbers generated by <code>random</code> to determine the direction of
           * the children (if any) when this asteroid dies.
           */
          public Asteroid createAsteroid(double x, double y, double xVelocity, double yVelocity, double radius, Random random){
        	  Asteroid asteroid = null;
              try{asteroid = new Asteroid(x,y,xVelocity, yVelocity, radius, random);
              }
              catch(Exception exc){
                      throw new ModelException(exc);
              }
              return asteroid;
          }

          /**
           * Check whether <code>o</code> is an asteroid.
           * 
           * You can use the <code>instanceof</code> operator to implement this method.
           */
          public boolean isAsteroid(Object o) {
        	  return ((SpaceObject)o).isAsteroid();
		}

          /**
           * Return the x-coordinate of <code>asteroid</code>.
           */
          public double getAsteroidX(Asteroid asteroid){
        	  return asteroid.getPosition().getX();
          }

          /**
           * Return the y-coordinate of <code>asteroid</code>.
           */
          public double getAsteroidY(Asteroid asteroid){
        	  return asteroid.getPosition().getY();
          }

          /**
           * Return the velocity of <code>asteroid</code> along the X-axis.
           */
          public double getAsteroidXVelocity(Asteroid asteroid){
        	  return asteroid.getVelocity().getX();
          }

          /**
           * Return the velocity of <code>asteroid</code> along the Y-axis.
           */
          public double getAsteroidYVelocity(Asteroid asteroid){
        	  return asteroid.getVelocity().getY();
          }

          /**
           * Return the radius of <code>asteroid</code>.
           */
          public double getAsteroidRadius(Asteroid asteroid){
        	  return asteroid.getRadius();
          }

          /**
           * Return the mass of <code>asteroid</code>.
           */
          public double getAsteroidMass(Asteroid asteroid){
        	  return asteroid.getMass();
          }

          /**
           * Return the world of <code>asteroid</code>.
           */
          public World getAsteroidWorld(Asteroid asteroid){
        	 return asteroid.getWorld();
          }

          /**
           * Check whether <code>o</code> is a bullet.
           * 
           * You can use the <code>instanceof</code> operator to implement this method.
           */
          public boolean isBullets(Object o) {
        	 return ((SpaceObject)o).isBullet();
		}

          /**
           * Return the x-coordinate of <code>bullet</code>.
           */
          public double getBulletX(Bullet bullet){
        	  return bullet.getPosition().getX();
          }

          /**
           * Return the y-coordinate of <code>bullet</code>.
           */
          public double getBulletY(Bullet bullet){
        	  return bullet.getPosition().getY();
          }

          /**
           * Return the velocity of <code>bullet</code> along the X-axis.
           */
          public double getBulletXVelocity(Bullet bullet){
        	  return bullet.getVelocity().getX();
          }

          /**
           * Return the velocity of <code>bullet</code> along the Y-axis.
           */
          public double getBulletYVelocity(Bullet bullet){
        	  return bullet.getVelocity().getY();
          }

          /**
           * Return the radius of <code>bullet</code>.
           */
          public double getBulletRadius(Bullet bullet){
        	  return bullet.getRadius();
          }

          /**
           * Return the mass of <code>bullet</code>.
           */
          public double getBulletMass(Bullet bullet){
        	  return bullet.getMass();
          }

          /**
           * Return the world of <code>bullet</code>.
           */
          public World getBulletWorld(Bullet bullet){
        	  return bullet.getWorld();
          }

          /**
           * Return the source of <code>bullet</code>.
           */
          public Ship getBulletSource(Bullet bullet) {
			try{
				return bullet.getShipsSource();
			}catch(Exception exc){
				throw new ModelException(exc);
			}
		}

		@SuppressWarnings("rawtypes")
		@Override
		public asteroids.IFacade.ParseOutcome<Program> parseProgram(String text) {
			ProgramFactoryImpl factory = new ProgramFactoryImpl();
		    ProgramParser<Expression, asteroids.model.programs.statements.Statement, Kind> parser = new ProgramParser<Expression, Statement, Kind>(factory);
		    try {
		        parser.parse(text);
		        List<String> errors = parser.getErrors();
		        if(! errors.isEmpty()) {
		          return ParseOutcome.failure(errors.get(0));
		        } else {
		          return ParseOutcome.success(new Program((java.util.HashMap<String,Kind>)parser.getGlobals(), parser.getStatement())); 
		        }
		    } catch(RecognitionException e) {
		      return ParseOutcome.failure(e.getMessage());
		    }

		}

		@Override
		public asteroids.IFacade.ParseOutcome<Program> loadProgramFromStream(
				InputStream stream) throws IOException {
					if (stream == null) 
						throw new IllegalArgumentException();
		            Writer writer = new StringWriter();

		            char[] buffer = new char[1024];
		            try {
		                Reader reader = new BufferedReader(
		                        new InputStreamReader(stream, "UTF-8"));
		                int n;
		                while ((n = reader.read(buffer)) != -1) {
		                    writer.write(buffer, 0, n);
		                }
		            } finally {
		                stream.close();
		            }
			return parseProgram(writer.toString());
		}

		@Override
		public asteroids.IFacade.ParseOutcome<Program> loadProgramFromUrl(
				URL url) throws IOException {
			return loadProgramFromStream(url.openStream()); 
		}

		@Override
		public boolean isTypeCheckingSupported() {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public asteroids.IFacade.TypeCheckOutcome typeCheckProgram(
				Program program) {
			// TODO Auto-generated method stub
			return null;
		}

		@Override
		public void setShipProgram(Ship ship, Program program) {
				program.setShip(ship);
				ship.setProgram(program);
			
		}
}